/*
* This file is a part of the open-eBackup project.
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
*
* Copyright (c) [2024] Huawei Technologies Co.,Ltd.
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*/
#include "SmbHandler.h"
#include "ProtectPluginFactory.h"
#include "PluginConstants.h"
#include "PluginUtilities.h"
#include "common/EnvVarManager.h"

using namespace PluginUtils;
namespace {
    constexpr auto MODULE = "SmbHandler";
    constexpr int MP_FAILED  = -1;
    constexpr int MP_SUCCESS  = 0;
}

static AutoRegAppManager<SmbHandler> g_autoReg{ResourceType::CIFS};
void SmbHandler::ListNativeResource(FileResourceInfo& resourceInfo,
                                    const ListResourceParam& listResourceParam)
{
     // kerborse
    if (listResourceParam.nasShareAuthInfo.authType == KRB5AUTHMODE) {
        if (KinitTGTInner(listResourceParam.nasShareAuthInfo.authKey,
                          listResourceParam.nasShareAuthInfo.authPwd,
                          listResourceParam.nasAuthExtentInfo.keytab,
                          listResourceParam.nasAuthExtentInfo.krb5Conf,
                          listResourceParam.applicationId) != MP_SUCCESS) {
            HCP_Log(ERR, MODULE) << "Faild to validate kerberos authentication." << HCPENDLOG;
            return;
        }
        HCP_Log(INFO, MODULE) << "Kinit TGT ticket SUCCESS." << HCPENDLOG;
    }
    std::vector<std::string> smbVers {SMB_VERSION_3_1_1,
                                 SMB_VERSION_3_02, SMB_VERSION_3_0,
                                 SMB_VERSION_2_1, SMB_VERSION_2_0};

    for (const auto& version : smbVers) {
        if (!ReadCifsResourceDir(resourceInfo, listResourceParam, version)) {
            continue;
        }
        HCP_Log(INFO, MODULE) << "ListApplicationResource success!" << HCPENDLOG;
        return;
    }
}

bool SmbHandler::ReadCifsResourceDir(FileResourceInfo& resourceInfo,
                                     const ListResourceParam& listResourceParam,
                                     const std::string& version)
{
    std::string userName = listResourceParam.nasShareAuthInfo.authKey;
    std::string serviceIp = listResourceParam.resourceExtendInfo.serviceIp;
    std::string authType = listResourceParam.nasShareAuthInfo.authType;
    std::string sharePath = listResourceParam.sharePath;
    
    if (*sharePath.begin() != '/') {
        sharePath = "/" + sharePath;
    }
    std::string smbUrl;
    // kerbors
    if (authType == KRB5AUTHMODE) {
        smbUrl = "smb://" + serviceIp + sharePath + "?sec=krb5&timeout=120&vers=" + version;
    } else {
        smbUrl = "smb://" + userName + "@" + serviceIp + sharePath + "?sec=ntlmssp&timeout=120&vers=" + version;
    }
   
    HCP_Log(INFO, MODULE) << "smburl : " << smbUrl << HCPENDLOG;
    struct smb2_context *smbContext = smb2_init_context();
    if (smbContext == nullptr) {
        HCP_Log(ERR, MODULE) << "Failed to init context" << HCPENDLOG;
        return false;
    }
    struct smb2_url *url = smb2_parse_url(smbContext, smbUrl.c_str());
    if (url == nullptr) {
        HCP_Log(ERR, MODULE) << "Failed to parse url." << WIPE_SENSITIVE(smb2_get_error(smbContext)) << HCPENDLOG;
        smb2_destroy_context(smbContext);
        return false;
    }
    smb2_set_user(smbContext, userName.c_str());
    smb2_set_password(smbContext, listResourceParam.nasShareAuthInfo.authPwd.c_str());
    smb2_set_security_mode(smbContext, SMB2_NEGOTIATE_SIGNING_ENABLED);
    // kerbors
    if (authType == KRB5AUTHMODE) {
        HCP_Log(INFO, MODULE) << "set env info for kerborse!" << HCPENDLOG;
        std::string agentHomePath = Module::EnvVarManager::GetInstance()->GetAgentHomePath();
        std::string krb5CcnameFile = agentHomePath + KRB5CCNAMEPREFIX + listResourceParam.applicationId;
        std::string krb5ConfigFile =
            agentHomePath + KRB5CONFIGPREFIX + listResourceParam.applicationId + KRB5CONFIGPOSTFIX;
        if (SetKrbEnvironment(krb5CcnameFile, krb5ConfigFile) != MP_SUCCESS) {
            smb2_destroy_context(smbContext);
            smb2_destroy_url(url);
            HCP_Log(ERR, MODULE) << "Failed to set kerberos environment variable" << HCPENDLOG;
            return false;
        }
    }

    if (smb2_connect_share(smbContext, url->server, url->share, url->user) != 0) {
        HCP_Log(ERR, MODULE) << "smb2_connect_share failed." << WIPE_SENSITIVE(smb2_get_error(smbContext)) << HCPENDLOG;
        smb2_destroy_context(smbContext);
        smb2_destroy_url(url);
        return false;
    }
    return InvokeSmbOpenDir(resourceInfo, smbContext, listResourceParam, url);
}

bool SmbHandler::InvokeSmbOpenDir(FileResourceInfo& resourceInfo,
                                  struct smb2_context* smbContext,
                                  const ListResourceParam& listResourceParam,
                                  struct smb2_url *url)
{
    std::string path = listResourceParam.path;
    if (*path.begin() == '/') {
        path = path.substr(1);
    }
    smb2dir *smbDir = smb2_opendir(smbContext, path.c_str());
    struct smb2dirent *smbdirent = nullptr;
    int countBegin = listResourceParam.pageNo * listResourceParam.pageSize - 1;
    int countEnd = countBegin + listResourceParam.pageSize;
    int totalNum = 0;
    while ((smbdirent = smb2_readdir(smbContext, smbDir)) != nullptr) {
        if (strcmp(smbdirent->name, ".") == 0 ||
            strcmp(smbdirent->name, "..") == 0) {
            continue;
        }
        if (totalNum <= countBegin || totalNum > countEnd) {
            ++totalNum;
            continue;
        }
        // open dir
        std::string fullPath = smbdirent->name;
        NasShareResourceInfo resourceDetailInfo;
        resourceDetailInfo.path = fullPath;
        resourceDetailInfo.modifyTime = PluginUtils::FormatTimeToStrBySetting(smbdirent->st.smb2_mtime,
                                                                              "%Y-%m-%d %H:%M:%S");
        resourceDetailInfo.size = smbdirent->st.smb2_size;
        if (smbdirent->st.smb2_type == SMB2_TYPE_DIRECTORY) {
            resourceDetailInfo.type = "d";
            resourceDetailInfo.hasChildren = true;
        } else {
            resourceDetailInfo.type = "f";
            resourceDetailInfo.hasChildren = false;
        }
        resourceInfo.resourceDetailVec.push_back(resourceDetailInfo);
        ++totalNum;
    }
    resourceInfo.totalCount = totalNum;
    smb2_destroy_context(smbContext);
    smb2_destroy_url(url);
    return true;
}

int SmbHandler::SetKrbEnvironment(const std::string& krb5CcnameFile, const std::string& krb5ConfigFile)
{
    /* setenv KRB5_CONFIG and KRB5CCNAME */
    struct stat info;
    if (stat(krb5ConfigFile.c_str(), &info) != 0) {
        HCP_Log(ERR, MODULE) << "no krb5 krb5ConfigFile: " << WIPE_SENSITIVE(krb5ConfigFile)
            << HCPENDLOG;
        return MP_FAILED;
    }
    if (stat(krb5CcnameFile.c_str(), &info) != 0) {
        HCP_Log(ERR, MODULE) << "no krb5 krb5CcacheFile: " << WIPE_SENSITIVE(krb5CcnameFile)
            << HCPENDLOG;
        return MP_FAILED;
    }
    std::string krb5Ccname = "FILE:" + krb5CcnameFile;
    setenv("KRB5_CONFIG", krb5ConfigFile.c_str(), 1);
    setenv("KRB5CCNAME", krb5Ccname.c_str(), 1);
    return MP_SUCCESS;
}

void SmbHandler::ListAggregateResource(FileResourceInfo& resourceInfo,
                                       const ListResourceParam& listResourceParam)
{
    return;
}